<?php

if (!defined('BASEPATH'))
  exit('No direct script access allowed');
/**
 * CodeIgniter
 *
 * An open source application development framework for PHP 5.1.6 or newer
 *
 * @package		CodeIgniter
 * @author		ExpressionEngine Dev Team
 * @copyright	Copyright (c) 2008 - 2011, EllisLab, Inc.
 * @license		http://codeigniter.com/user_guide/license.html
 * @link		http://codeigniter.com
 * @since		Version 1.0
 * @filesource
 */
// ------------------------------------------------------------------------

/**
 * CodeIgniter Encryption Class
 *
 * Provides two-way keyed encoding using XOR Hashing and Mcrypt
 *
 * @package		CodeIgniter
 * @subpackage	Libraries
 * @category	Libraries
 * @author		ExpressionEngine Dev Team
 * @link		http://codeigniter.com/user_guide/libraries/encryption.html
 */
class CI_Encrypt {

  var $CI;
  var $encryption_key = '';
  var $_hash_type = 'sha1';
  var $_mcrypt_exists = FALSE;
  var $_mcrypt_cipher;
  var $_mcrypt_mode;

  /**
   * Constructor
   *
   * Simply determines whether the mcrypt library exists.
   *
   */
  public function __construct() {
    $this->CI = & get_instance();
    $this->_mcrypt_exists = (!function_exists('mcrypt_encrypt')) ? FALSE : TRUE;
    log_message('debug', "Encrypt Class Initialized");
  }

  // --------------------------------------------------------------------

  /**
   * Fetch the encryption key
   *
   * Returns it as MD5 in order to have an exact-length 128 bit key.
   * Mcrypt is sensitive to keys that are not the correct length
   *
   * @access	public
   * @param	string
   * @return	string
   */
  function get_key($key = '') {
    if ($key == '') {
      if ($this->encryption_key != '') {
        return $this->encryption_key;
      }

      $CI = & get_instance();
      $key = $CI->config->item('encryption_key');

      if ($key == FALSE) {
        show_error('In order to use the encryption class requires that you set an encryption key in your config file.');
      }
    }

    return md5($key);
  }

  // --------------------------------------------------------------------

  /**
   * Set the encryption key
   *
   * @access	public
   * @param	string
   * @return	void
   */
  function set_key($key = '') {
    $this->encryption_key = $key;
  }

  // --------------------------------------------------------------------

  /**
   * Encode
   *
   * Encodes the message string using bitwise XOR encoding.
   * The key is combined with a random hash, and then it
   * too gets converted using XOR. The whole thing is then run
   * through mcrypt (if supported) using the randomized key.
   * The end result is a double-encrypted message string
   * that is randomized with each call to this function,
   * even if the supplied message and key are the same.
   *
   * @access	public
   * @param	string	the string to encode
   * @param	string	the key
   * @return	string
   */
  function encode($string, $key = '') {
    $key = $this->get_key($key);

    if ($this->_mcrypt_exists === TRUE) {
      $enc = $this->mcrypt_encode($string, $key);
    } else {
      $enc = $this->_xor_encode($string, $key);
    }

    return $this->mybase64_encode($enc);
  }

  // --------------------------------------------------------------------

  /**
   * Decode
   *
   * Reverses the above process
   *
   * @access	public
   * @param	string
   * @param	string
   * @return	string
   */
  function decode($string, $key = '') {
    $key = $this->get_key($key);

    if (preg_match('/[^a-zA-Z0-9\/\+=]/', $string)) {
      return FALSE;
    }

    $dec = $this->mybase64_decode($string);

    if ($this->_mcrypt_exists === TRUE) {
      if (($dec = $this->mcrypt_decode($dec, $key)) === FALSE) {
        return FALSE;
      }
    } else {
      $dec = $this->_xor_decode($dec, $key);
    }

    return $dec;
  }

  // --------------------------------------------------------------------

  /**
   * Encode from Legacy
   *
   * Takes an encoded string from the original Encryption class algorithms and
   * returns a newly encoded string using the improved method added in 2.0.0
   * This allows for backwards compatibility and a method to transition to the
   * new encryption algorithms.
   *
   * For more details, see http://codeigniter.com/user_guide/installation/upgrade_200.html#encryption
   *
   * @access	public
   * @param	string
   * @param	int		(mcrypt mode constant)
   * @param	string
   * @return	string
   */
  function encode_from_legacy($string, $legacy_mode = MCRYPT_MODE_ECB, $key = '') {
    if ($this->_mcrypt_exists === FALSE) {
      log_message('error', 'Encoding from legacy is available only when Mcrypt is in use.');
      return FALSE;
    }

    // decode it first
    // set mode temporarily to what it was when string was encoded with the legacy
    // algorithm - typically MCRYPT_MODE_ECB
    $current_mode = $this->_get_mode();
    $this->set_mode($legacy_mode);

    $key = $this->get_key($key);

    if (preg_match('/[^a-zA-Z0-9\/\+=]/', $string)) {
      return FALSE;
    }

    $dec = base64_decode($string);

    if (($dec = $this->mcrypt_decode($dec, $key)) === FALSE) {
      return FALSE;
    }

    $dec = $this->_xor_decode($dec, $key);

    // set the mcrypt mode back to what it should be, typically MCRYPT_MODE_CBC
    $this->set_mode($current_mode);

    // and re-encode
    return base64_encode($this->mcrypt_encode($dec, $key));
  }

  // --------------------------------------------------------------------

  /**
   * XOR Encode
   *
   * Takes a plain-text string and key as input and generates an
   * encoded bit-string using XOR
   *
   * @access	private
   * @param	string
   * @param	string
   * @return	string
   */
  function _xor_encode($string, $key) {
    $rand = '';
    while (strlen($rand) < 32) {
      $rand .= mt_rand(0, mt_getrandmax());
    }

    $rand = $this->hash($rand);

    $enc = '';
    for ($i = 0; $i < strlen($string); $i++) {
      $enc .= substr($rand, ($i % strlen($rand)), 1) . (substr($rand, ($i % strlen($rand)), 1) ^ substr($string, $i, 1));
    }

    return $this->_xor_merge($enc, $key);
  }

  // --------------------------------------------------------------------

  /**
   * XOR Decode
   *
   * Takes an encoded string and key as input and generates the
   * plain-text original message
   *
   * @access	private
   * @param	string
   * @param	string
   * @return	string
   */
  function _xor_decode($string, $key) {
    $string = $this->_xor_merge($string, $key);

    $dec = '';
    for ($i = 0; $i < strlen($string); $i++) {
      $dec .= (substr($string, $i++, 1) ^ substr($string, $i, 1));
    }

    return $dec;
  }

  // --------------------------------------------------------------------

  /**
   * XOR key + string Combiner
   *
   * Takes a string and key as input and computes the difference using XOR
   *
   * @access	private
   * @param	string
   * @param	string
   * @return	string
   */
  function _xor_merge($string, $key) {
    $hash = $this->hash($key);
    $str = '';
    for ($i = 0; $i < strlen($string); $i++) {
      $str .= substr($string, $i, 1) ^ substr($hash, ($i % strlen($hash)), 1);
    }

    return $str;
  }

  // --------------------------------------------------------------------

  /**
   * Encrypt using Mcrypt
   *
   * @access	public
   * @param	string
   * @param	string
   * @return	string
   */
  function mcrypt_encode($data, $key) {
    $init_size = mcrypt_get_iv_size($this->_get_cipher(), $this->_get_mode());
    $init_vect = mcrypt_create_iv($init_size, MCRYPT_RAND);
    return $this->_add_cipher_noise($init_vect . mcrypt_encrypt($this->_get_cipher(), $key, $data, $this->_get_mode(), $init_vect), $key);
  }

  // --------------------------------------------------------------------

  /**
   * Decrypt using Mcrypt
   *
   * @access	public
   * @param	string
   * @param	string
   * @return	string
   */
  function mcrypt_decode($data, $key) {
    $data = $this->_remove_cipher_noise($data, $key);
    $init_size = mcrypt_get_iv_size($this->_get_cipher(), $this->_get_mode());

    if ($init_size > strlen($data)) {
      return FALSE;
    }

    $init_vect = substr($data, 0, $init_size);
    $data = substr($data, $init_size);
    return rtrim(mcrypt_decrypt($this->_get_cipher(), $key, $data, $this->_get_mode(), $init_vect), "\0");
  }

  // --------------------------------------------------------------------

  /**
   * Adds permuted noise to the IV + encrypted data to protect
   * against Man-in-the-middle attacks on CBC mode ciphers
   * http://www.ciphersbyritter.com/GLOSSARY.HTM#IV
   *
   * Function description
   *
   * @access	private
   * @param	string
   * @param	string
   * @return	string
   */
  function _add_cipher_noise($data, $key) {
    $keyhash = $this->hash($key);
    $keylen = strlen($keyhash);
    $str = '';

    for ($i = 0, $j = 0, $len = strlen($data); $i < $len; ++$i, ++$j) {
      if ($j >= $keylen) {
        $j = 0;
      }

      $str .= chr((ord($data[$i]) + ord($keyhash[$j])) % 256);
    }

    return $str;
  }

  // --------------------------------------------------------------------

  /**
   * Removes permuted noise from the IV + encrypted data, reversing
   * _add_cipher_noise()
   *
   * Function description
   *
   * @access	public
   * @param	type
   * @return	type
   */
  function _remove_cipher_noise($data, $key) {
    $keyhash = $this->hash($key);
    $keylen = strlen($keyhash);
    $str = '';

    for ($i = 0, $j = 0, $len = strlen($data); $i < $len; ++$i, ++$j) {
      if ($j >= $keylen) {
        $j = 0;
      }

      $temp = ord($data[$i]) - ord($keyhash[$j]);

      if ($temp < 0) {
        $temp = $temp + 256;
      }

      $str .= chr($temp);
    }

    return $str;
  }

  // --------------------------------------------------------------------

  /**
   * Set the Mcrypt Cipher
   *
   * @access	public
   * @param	constant
   * @return	string
   */
  function set_cipher($cipher) {
    $this->_mcrypt_cipher = $cipher;
  }

  // --------------------------------------------------------------------

  /**
   * Set the Mcrypt Mode
   *
   * @access	public
   * @param	constant
   * @return	string
   */
  function set_mode($mode) {
    $this->_mcrypt_mode = $mode;
  }

  // --------------------------------------------------------------------

  /**
   * Get Mcrypt cipher Value
   *
   * @access	private
   * @return	string
   */
  function _get_cipher() {
    if ($this->_mcrypt_cipher == '') {
      $this->_mcrypt_cipher = MCRYPT_RIJNDAEL_256;
    }

    return $this->_mcrypt_cipher;
  }

  // --------------------------------------------------------------------

  /**
   * Get Mcrypt Mode Value
   *
   * @access	private
   * @return	string
   */
  function _get_mode() {
    if ($this->_mcrypt_mode == '') {
      $this->_mcrypt_mode = MCRYPT_MODE_CBC;
    }

    return $this->_mcrypt_mode;
  }

  // --------------------------------------------------------------------

  /**
   * Set the Hash type
   *
   * @access	public
   * @param	string
   * @return	string
   */
  function set_hash($type = 'sha1') {
    $this->_hash_type = ($type != 'sha1' AND $type != 'md5') ? 'sha1' : $type;
  }

  // --------------------------------------------------------------------

  /**
   * Hash encode a string
   *
   * @access	public
   * @param	string
   * @return	string
   */
  function hash($str) {
    return ($this->_hash_type == 'sha1') ? $this->sha1($str) : md5($str);
  }

  // --------------------------------------------------------------------

  /**
   * Generate an SHA1 Hash
   *
   * @access	public
   * @param	string
   * @return	string
   */
  function sha1($str) {
    if (!function_exists('sha1')) {
      if (!function_exists('mhash')) {
        require_once(BASEPATH . 'libraries/Sha1.php');
        $SH = new CI_SHA;
        return $SH->generate($str);
      } else {
        return bin2hex(mhash(MHASH_SHA1, $str));
      }
    } else {
      return sha1($str);
    }
  }

  /**
   * Custom base64 for clean strings
   * 
   * @param type $s
   * @return type
   */
  function mybase64_encode($s) {
    return str_replace(array('+', '/', '='), array('plus', 'slash', 'equal'), base64_encode($s));
  }

  /**
   * Custom base64 for clean strings
   * 
   * @param type $s
   * @return type
   */
  function mybase64_decode($s) {
    return base64_decode(str_replace(array('plus', 'slash', 'equal'), array('+', '/', '='), $s));
  }

}

// END CI_Encrypt class

/* End of file Encrypt.php */
/* Location: ./system/libraries/Encrypt.php */